package com.bungarus.group.service;

import com.bungarus.group.cachestore.CacheStore;
import com.bungarus.group.jdbc.GroupMemberManager;
import com.bungarus.group.model.Group;
import com.bungarus.group.model.GroupMember;
import com.bungarus.group.model.Member;
import com.bungarus.group.model.MemberCacheTransformer;
import com.bungarus.group.repo.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;

/**
 * Created by tang on 2018/12/13.
 */
@Service
public class MemberServiceImpl implements MemberService{
    private static final int RETRY_TOTAL_COUNT = 3;
    private MemberRepository repository;
    private GroupMemberManager groupMemberManager;
    private GroupService groupService;

    private CacheStore cacheStore;
    private MemberCacheTransformer transformer;

    private PasswordEncoder pwdEncoder;

    @Autowired
    public MemberServiceImpl(MemberRepository repository, GroupMemberManager groupMemberManager, GroupService groupService,
                             CacheStore cacheStore, MemberCacheTransformer transformer) {
        this.repository = repository;
        this.groupMemberManager = groupMemberManager;
        this.transformer = transformer;
        this.cacheStore = cacheStore;
        this.groupService = groupService;
    }

    @Autowired
    public void setPwdEncoder(PasswordEncoder pwdEncoder) {
        this.pwdEncoder = pwdEncoder;
    }

    @Override
    public Member isExists(String account, String pwd, long tid) {
        if(StringUtils.isEmpty(account) || StringUtils.isEmpty(pwd)
                || tid <= 0) {
            throw new IllegalArgumentException("member's account name or password cannot be empty.");
        }
        Member member = cacheStore.findByUniqueId(
                CacheStore.ofUniqueId(String.valueOf(tid), account), transformer);
        if(null == member) {
            this.repository.findByAccountNameAndTenantId(account, tid);
        }
        else if(pwdEncoder.matches(pwd, member.getPassword())) {
            return member;
        }
        return null;
    }

    @Override
    public Member isExists(Member member) {
        if(null == member) {
            throw new IllegalArgumentException("member cannot be null.");
        }
        return isExists(member.getAccountName(), member.getPassword(), member.getTenantId());
    }

    @Override
    public Member createMember(long id, long tenantId, String account, String password) {
        return createMember(id, tenantId, MemberService.UNKNOWN, account, password);
    }

    @Override
    public Member createMember(long id, long tenantId, String name, String account, String password) {
        if(id <= 0 || tenantId <= 0
                || StringUtils.isEmpty(account)
                || StringUtils.isEmpty(password)) {
            throw new IllegalArgumentException("member's data is invalid.");
        }
        String _name = name;
        if(StringUtils.isEmpty(name)) {
            _name = MemberService.UNKNOWN;
        }
        Member member = Member.newBuilder()
                .id(id)
                .tenantId(tenantId)
                .pwd(pwdEncoder.encode(password))
                .account(account)
                .name(_name)
                .build();
        Member result = this.repository.save(member);
        if(null != result) {
            cacheStore.save(transformer, member);
            //Create a special group which contains the member's latest contacts
            //The identity of this group is same as member's identity, and belongs to the same tenant
            Group group = Group.newBuilder().id(member.getId())
                            .tenant(member.getTenantId())
                            .name(member.getAccountName().toLowerCase() + Group.SUFFIX_LATEST_CONTACTS)
                            .build();
            groupService.createGroup(group);
        }
        return result;
    }

    @Override
    public Member createMember(Member member) {
        if(null == member) {
            throw new IllegalArgumentException("input must not be null.");
        }
        return createMember(member.getId(), member.getTenantId(),
                member.getName(), member.getAccountName(), member.getPassword());
    }

    /**
     * Delete member in redis cache and db
     * @param account    System acccount of member
     * @param tenantId   Unique id of tenant the member belongs to
     */
    @Override
    @Transactional
    public void deleteMember(String account, long tenantId) {
        if(null == account || account.length() <=0 || tenantId <= 0){
            throw new IllegalArgumentException("member's account and tenant id are invalid.");
        }
        String uniqueId = CacheStore.ofUniqueId(String.valueOf(tenantId), account);
        Member member = cacheStore.findByUniqueId(uniqueId, transformer);
        if(null != member) {
            GroupMember gm = GroupMember.ofMember(member);
            deleteMember0(1, gm);
            //Delete the special group which contains the member's latest contacts
            //The identity of the group is same as member's identity, and belongs to the same tenant
            Group group = Group.newBuilder().id(member.getId())
                    .tenant(member.getTenantId())
                    .build();
            groupService.deleteGroup(group);
        }
    }

    private void deleteMember0(int retryCount, GroupMember groupMember) {
        if(retryCount > RETRY_TOTAL_COUNT) {
            throw new IllegalStateException("Delete member error: exceed max retry count.");
        }
        String uniqueId = CacheStore.ofUniqueId(String.valueOf(groupMember.getTenantId()), groupMember.getmAccount());
        try {
            repository.deleteByIdAndTenantId(groupMember.getMid(), groupMember.getTenantId());
            groupMemberManager.deleteMemberOfTenant(groupMember);
            cacheStore.delete(uniqueId, transformer);
        }
        catch (Exception e) {
            e.printStackTrace();
            retryCount++;
            deleteMember0(retryCount, groupMember);
        }
    }

    @Override
    @Transactional
    public void deleteMember(Member member) {
        if(null == member) {
            throw new IllegalArgumentException("input cannot be null.");
        }
        deleteMember(member.getAccountName(), member.getTenantId());
    }

    @Override
    public List<Group> retrieveGroupsMemberBelongsTo(long mid, long tid) {
        return retrieveGroupsMemberBelongsTo(mid, tid, null);
    }

    @Override
    public List<Group> retrieveGroupsMemberBelongsTo(long mid, long tid, Pageable page) {
        if(mid <= 0 || tid <= 0) {
            throw new IllegalArgumentException("mid and tid are invalid.");
        }
        GroupMember gm = new GroupMember();
        gm.setMid(mid);
        gm.setTenantId(tid);

        Pageable _page = page;
        if(null == _page) {
            _page = PageRequest.of(DEFAULT_START_PAGE, DEFAULT_PAGE_SIZE, Sort.by(DEFAULT_SORT_BY_GROUP));
        }
        return this.groupMemberManager.findGroupsMemberBelongsTo(gm, _page);
    }

    @Override
    public List<Group> retrieveGroupsMemberBelongsTo(Member member) {
        return retrieveGroupsMemberBelongsTo(member, null);
    }

    @Override
    public List<Group> retrieveGroupsMemberBelongsTo(Member member, Pageable page) {
        if(null == member) {
            throw new IllegalArgumentException("member cannot be null.");
        }

        Pageable _page = page;
        if(null == _page) {
            _page = PageRequest.of(DEFAULT_START_PAGE, DEFAULT_PAGE_SIZE, Sort.by(DEFAULT_SORT_BY_GROUP));
        }
        return retrieveGroupsMemberBelongsTo(member.getId(), member.getTenantId(), _page);
    }

    @Override
    public int totalCountOfGroupsMemberBelongsTo(long mid, long tid) {
        return this.groupMemberManager.totalCountOfGroupsMemberBelongsTo(mid, tid);
    }
}
